package com.siashan.unipay4j.wx.pay;

import com.alibaba.fastjson.JSON;
import com.siashan.unipay4j.core.IPayConfig;
import com.siashan.unipay4j.core.Result;
import com.siashan.unipay4j.core.TradeStateEnum;
import com.siashan.unipay4j.core.pay.INotify;
import com.siashan.unipay4j.core.pay.PayResponse;
import com.siashan.unipay4j.core.util.EnumUtil;
import com.siashan.unipay4j.core.util.MoneyUtil;
import com.siashan.unipay4j.core.util.RequestUtil;
import com.siashan.unipay4j.wx.AbstractWxPay;
import com.siashan.unipay4j.wx.WxPayVersionEnum;
import com.siashan.unipay4j.wx.model.QueryOrderResult;
import com.siashan.unipay4j.wx.sdk.WXPayConstants;
import com.siashan.unipay4j.wx.sdk.WXPayUtil;
import com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * 微信支付回调
 *
 * <p>微信支付通过支付通知接口将用户支付成功消息通知给商户</p>
 * 注意：
 * • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是，当商户系统收到通知进行处理时，先检查对应业务数据的状态，并判断该通知是否已经处理。如果未处理，则再进行处理；如果已处理，则直接返回结果成功。在对业务数据进行状态检查和处理之前，要采用数据锁进行并发控制，以避免函数重入造成的数据混乱。
 *
 * • 如果在所有通知频率后没有收到微信侧回调，商户应调用查询订单接口确认订单状态。
 *
 *
 * 特别提醒：商户系统对于开启结果通知的内容一定要做签名验证，并校验通知的信息是否与商户侧的信息一致，防止数据泄露导致出现“假通知”，造成资金损失。
 *
 * @author siashan
 * @since v1.0.1
 **/
@Slf4j
public class WxPayNotify extends AbstractWxPay implements INotify {

    public WxPayNotify() {
        super();
    }

    public WxPayNotify(IPayConfig payConfig) {
        super(payConfig);
    }

    /**
     * 支付回调
     *
     * @param request Request请求
     *
     * @return 回调处理结果
     */
    @Override
    public Result execNotify(HttpServletRequest request) {
        WxPayVersionEnum ver = EnumUtil.getIEnum(WxPayVersionEnum.class, wxPayConfig.getVersion());
        Result result = null;
        switch (ver) {
            case V2:
                result = doNotifyV2(request);
                break;
            case V3:
                result = doNotifyV3(request);
                break;
            default:
                break;
        }
        return result;
    }


    /**
     * 支付回调 V2
     *
     * @param request Request请求
     *
     * @return 回调处理结果
     */
    public Result doNotifyV2(HttpServletRequest request) {
        try {
            String xmlStr = RequestUtil.getStrFromInputSteam(request.getInputStream());
            Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
            // 验签
            if (WXPayUtil.isSignatureValid(map, wxPayConfig.getKey(), wxPayConfig.getSignType())) {
                String returnCode = map.get(WXPayConstants.RETURN_CODE);
                if (isReturnSuccess(returnCode)) {
                    String resultCode = map.get(WXPayConstants.RESULT_CODE);
                    return isResultSuccess(resultCode) ? Result.tradeSuccess(wrapperPayResponse(map, TradeStateEnum.SUCCESS)) : Result.tradeSuccess(wrapperPayResponse(map, TradeStateEnum.FAIL));
                } else {
                    // 通信失败
                    return Result.returnFail(returnCode, map.get(WXPayConstants.RETURN_MSG));
                }

            } else {
                log.warn("WxNotify::=> 回调验签失败");
                return Result.invalidSign();
            }
            // 解析微信回调参数
        } catch (Exception e) {
            log.warn("WxNotify::=> 回调时发生异常", e);
        }
        return Result.exception();
    }

    /**
     * 支付回调 V3
     *
     * @param request Request请求
     *
     * @return 回调处理结果
     */
    public Result doNotifyV3(HttpServletRequest request) {

        try {
            // 请求头Wechatpay-Nonce
            String nonce = request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_NONCE);
            // 请求头Wechatpay-Timestamp
            String timestamp = request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_TIMESTAMP);
            // 请求头Wechatpay-Signature
            String signature = request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_SIGNATURE);
            // 此处一定要使用微信回调返回的序列号，不能使用商户私有序列号，不然会验签失败
            String serialNumber =request.getHeader(WechatPayHttpHeaders.WECHAT_PAY_SERIAL);
            String body = RequestUtil.getStrFromInputSteam(request.getInputStream());
            // 构建request，传入必要参数
            NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(serialNumber)
                    .withNonce(nonce)
                    .withTimestamp(timestamp)
                    .withSignature(signature)
                    .withBody(body)
                    .build();
            NotificationHandler handler = new NotificationHandler(loadVerifier(), wxPayConfig.getV3Key().getBytes(StandardCharsets.UTF_8));
            // 验签和解析请求体
            Notification notification = handler.parse(notificationRequest);
            log.info("WxNotify::=> 回调通知报文：{}",notification.toString());
            String decryptData = notification.getDecryptData();
            log.info("WxNotify::=> 回调通知业务明文：{}",decryptData);
            QueryOrderResult orderResult = JSON.parseObject(decryptData, QueryOrderResult.class);
            return  Result.tradeSuccess(wrapperPayResponse(orderResult));
        } catch (Exception e) {
            log.warn("WxNotify::=> 回调时发生异常", e);
        }
        return Result.exception();
    }

    private PayResponse wrapperPayResponse(QueryOrderResult result){
        TradeStateEnum stateEnum = EnumUtil.getIEnum(WxTradeStateEnum.class, result.getTradeState()).getTradeState();
        PayResponse response = new PayResponse();
        response.setTradeState(stateEnum);
        response.setOutTradeNo(result.getOutTradeNo());
        response.setTradeNo(result.getTransactionId());
        response.setTotalAmount(MoneyUtil.cent2Yuan(result.getAmount().getTotal()));
        response.setBody(result);
        return response;
    }

    /**
     * 回调成功，同步返回给支付平台的报文
     *
     * @apiNote V3 HTTP应答状态码需返回200或204，无需返回应答报文
     *
     * @return 返回报文
     */
    @Override
    public String successMsg() {
        return "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
    }

    /**
     * 回调失败，同步返回给支付平台的报文
     *
     * @apiNote V3 HTTP应答状态码需返回5XX或4XX，同时需返回应答报文
     *
     * @return 返回报文
     */
    @Override
    public String failMsg() {
        WxPayVersionEnum ver = EnumUtil.getIEnum(WxPayVersionEnum.class, wxPayConfig.getVersion());
        String msg  = "";
        switch (ver) {
            case V2:
                msg =  "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[ERROR]]></return_msg></xml>";
                break;
            case V3:
                msg = "{ \"code\": \"FAIL\",\"message\": \"失败\"}";
                break;
            default:
                break;
        }
        return msg;
    }

}
